﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using OperationPlugins.Compositions;
using OperationPlugins.Models;

namespace OperationPlugins.Handlers
{
    public static class Handler
    {
        private static readonly Predicate<Type> DefaultCondition = x => true;
        
        /// <summary>
        /// Authorizes the handler.
        /// </summary>
        private static IEnumerable<IAuthorizeHandler> Authorizers { get; set; }

        /// <summary>
        /// Activates the handler.
        /// </summary>
        private static IHandlerActivator Activator { get; set; }

        /// <summary>
        /// Invokes the handler.
        /// </summary>
        private static IHandlerInvoker Invoker { get; set; }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        public static void Invoke(object value)
        {
            Invoke(value, Operations.Any, Branches.Any, OperationContextManager.GetOperationContextFactory(), DefaultCondition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="condition">The custom search condition.</param>
        public static void Invoke(object value, Predicate<Type> condition)
        {
            Invoke(value, Operations.Any, Branches.Any, OperationContextManager.GetOperationContextFactory(), condition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        public static void Invoke(object value, IOperationContextFactory contextFactory)
        {
            Invoke(value, Operations.Any, Branches.Any, contextFactory, DefaultCondition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <param name="condition">The custom search condition.</param>
        public static void Invoke(object value, IOperationContextFactory contextFactory, Predicate<Type> condition)
        {
            Invoke(value, Operations.Any, Branches.Any, contextFactory, condition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="operation">Identifies the current operation.</param>
        public static void Invoke(object value, OperationId operation)
        {
            Invoke(value, operation, Branches.Any, OperationContextManager.GetOperationContextFactory(), DefaultCondition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="condition">The custom search condition.</param>
        public static void Invoke(object value, OperationId operation, Predicate<Type> condition)
        {
            Invoke(value, operation, Branches.Any, OperationContextManager.GetOperationContextFactory(), condition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        public static void Invoke(object value, OperationId operation, IOperationContextFactory contextFactory)
        {
            Invoke(value, operation, Branches.Any, contextFactory, DefaultCondition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <param name="condition">The custom search condition.</param>
        public static void Invoke(object value, OperationId operation, IOperationContextFactory contextFactory, Predicate<Type> condition)
        {
            Invoke(value, operation, Branches.Any, contextFactory, condition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="branch">Identifies the current branch.</param>
        public static void Invoke(object value, BranchId branch)
        {
            Invoke(value, Operations.Any, branch, OperationContextManager.GetOperationContextFactory(), DefaultCondition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="condition">The custom search condition.</param>
        public static void Invoke(object value, BranchId branch, Predicate<Type> condition)
        {
            Invoke(value, Operations.Any, branch, OperationContextManager.GetOperationContextFactory(), condition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        public static void Invoke(object value, BranchId branch, IOperationContextFactory contextFactory)
        {
            Invoke(value, Operations.Any, branch, contextFactory, DefaultCondition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <param name="condition">The custom search condition.</param>
        public static void Invoke(object value, BranchId branch, IOperationContextFactory contextFactory, Predicate<Type> condition)
        {
            Invoke(value, Operations.Any, branch, contextFactory, condition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="branch">Identifies the current branch.</param>
        public static void Invoke(object value, OperationId operation, BranchId branch)
        {
            Invoke(value, operation, branch, OperationContextManager.GetOperationContextFactory(), DefaultCondition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="condition">The custom search condition.</param>
        public static void Invoke(object value, OperationId operation, BranchId branch, Predicate<Type> condition)
        {
            Invoke(value, operation, branch, OperationContextManager.GetOperationContextFactory(), condition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        public static void Invoke(object value, OperationId operation, BranchId branch, IOperationContextFactory contextFactory)
        {
            Invoke(value, operation, branch, contextFactory, DefaultCondition);
        }

        /// <summary>
        /// Handles an object.
        /// </summary>
        /// <param name="value">The object to be handled.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <param name="condition">The custom search condition.</param>
        public static void Invoke(object value, OperationId operation, BranchId branch, IOperationContextFactory contextFactory, Predicate<Type> condition)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            if (operation == null)
            {
                throw new ArgumentNullException("operation");
            }

            if (branch == null)
            {
                throw new ArgumentNullException("branch");
            }

            if (contextFactory == null)
            {
                throw new ArgumentNullException("contextFactory");
            }

            if (condition == null)
            {
                throw new ArgumentNullException("condition");
            }

            OperationPluginsEnvironment.CheckStatus();

            OperationContext context = OperationContextManager.GetOperationContext(operation, branch, contextFactory);

            foreach (HandlerInfo info in HandlerInfo.GetHandlers(value, context).Where(x => condition.Invoke(x.PluginType)))
            {                               
                IHandler handler = GetHandler(info.PluginType, context);
                if (handler != null)
                {
                    Invoker.Invoke(handler, value, context);
                }
            }
        }

        private static IHandler GetHandler(Type pluginType, OperationContext context)
        {
            IHandler handler = Activator.Activate(pluginType);

            foreach (IAuthorizeHandler authorizer in Authorizers)
            {
                if (authorizer.Authorize(handler, context) == false)
                {
                    return null;
                }
            }

            return handler;
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        [Export(typeof(IInitializable))]
        private sealed class Initializer : IInitializable
        {
            void IInitializable.Initialize(InitializationContext context)
            {
                Authorizers = context.CompositionContainer.GetPlugins<IAuthorizeHandler>();
                Activator = context.CompositionContainer.GetPlugin<IHandlerActivator>();
                Invoker = context.CompositionContainer.GetPlugin<IHandlerInvoker>();
            }
        }

        /// <summary>
        /// Activates the handler.
        /// </summary>
        [Export(typeof(IHandlerActivator))]
        [DefaultPriority]
        private sealed class DefaultHandlerActivator : IHandlerActivator
        {
            IHandler IHandlerActivator.Activate(Type pluginType)
            {
                return (IHandler)ObjectFactory.CreateClassInstance(pluginType);
            }
        }

        /// <summary>
        /// Invokes the handler.
        /// </summary>
        [Export(typeof(IHandlerInvoker))]
        [DefaultPriority]
        private sealed class DefaultHandlerInvoker : IHandlerInvoker
        {
            void IHandlerInvoker.Invoke(IHandler handler, object target, OperationContext context)
            {
                handler.Handle(target, context);
            }
        }

    }
}
